/**
 * 
 */
package br.com.framework.apresentacao.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;

import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIComponent;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;

import br.com.framework.util.resource.MessageResource;
import br.com.framework.util.resource.MessageResourceFactory;

/**
 * @author Cleber
 *
 */
public class FacesMessagesUtil {
	
	private static Map<String, FacesMessagesUtil> instancesMap = new HashMap<String, FacesMessagesUtil>();
	
	private static FacesMessagesUtil currentInstance;
	
	public static final String DEFAULT_RESOURCE_BUNDLE = "mensagens";
	
	private MessageResource messageResource;

	/**
	 * @param bundleName
	 * @param locale
	 */
	private FacesMessagesUtil(String bundleName, Locale locale) {
		this.messageResource = MessageResourceFactory.createMessageResource(
				bundleName, locale, getClass().getClassLoader());
	}
	
	/**
	 * @param bundleName
	 * @return
	 */
	public static FacesMessagesUtil getInstance(String bundleName, Locale locale) {
		FacesMessagesUtil instance = instancesMap.get(bundleName);
		if (instance == null) {
			instance = new FacesMessagesUtil(bundleName, locale);
			currentInstance = instance;
			instancesMap.put(bundleName, instance);
		}
		return instance;
	}
	
	/**
	 * @return
	 */
	public static FacesMessagesUtil getCurrentInstance() {
		if (currentInstance == null) {
			currentInstance = getInstance(DEFAULT_RESOURCE_BUNDLE, null);
		}
		return currentInstance;
	}
	
	/**
	 * Adiciona uma mensagem ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param severity
	 * 	Severidade da mensagem do tipo {@link Severity}. Ex: FacesMessage.SEVERITY_INFO, FacesMessage.SEVERITY_WARN, etc.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgDetailKey
	 * 	Chave do detalhamento a ser utilizado
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 * @param msgDetailArgs
	 * 	Argumentos do detalhamento.
	 */
	public void addMessage(String componentId, Severity severity,
			String msgSummaryKey, String msgDetailKey, Object[] msgSummaryArgs,
			Object[] msgDetailArgs) {
		FacesMessage facesMessage = new FacesMessage();
		facesMessage.setSeverity(severity);
		if (msgSummaryKey != null) {
			if (msgSummaryArgs != null && msgSummaryArgs.length > 0) {
				facesMessage.setSummary(this.messageResource.get(msgSummaryKey,
						msgSummaryArgs));
			} else {
				facesMessage
						.setSummary(this.messageResource.get(msgSummaryKey));
			}
		}
		if (msgDetailKey != null) {
			if (msgDetailArgs != null && msgDetailArgs.length > 0) {
				facesMessage.setDetail(this.messageResource.get(msgDetailKey,
						msgDetailArgs));
			} else {
				facesMessage.setDetail(this.messageResource.get(msgDetailKey));
			}
		}
		String clientId = null;
		if (componentId != null) {
			UIComponent component = findComponent(getFacesContext()
					.getViewRoot(), componentId);
			clientId = component.getClientId(getFacesContext());
		}
		getFacesContext().addMessage(clientId, facesMessage);
	}
	
	/**
	 * Adiciona uma mensagem ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param severity
	 * 	Severidade da mensagem do tipo {@link Severity}. Ex: FacesMessage.SEVERITY_INFO, FacesMessage.SEVERITY_WARN, etc.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 */
	public void addMessage(String componentId, Severity severity,
			String msgSummaryKey, Object[] msgSummaryArgs) {
		addMessage(componentId, severity, msgSummaryKey, null, msgSummaryArgs, null);
	}
	
	/**
	 * Adiciona uma mensagem de severidade INFO ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgDetailKey
	 * 	Chave do detalhamento a ser utilizado
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 * @param msgDetailArgs
	 * 	Argumentos do detalhamento.
	 */
	public void addMessageInfo(String componentId, String msgSummaryKey, String msgDetailKey, Object[] msgSummaryArgs,
			Object[] msgDetailArgs) {
		addMessage(componentId, FacesMessage.SEVERITY_INFO, msgSummaryKey, msgDetailKey, msgSummaryArgs, msgDetailArgs);
	}
	
	/**
	 * Adiciona uma mensagem de severidade INFO ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 */
	public void addMessageInfo(String componentId, String msgSummaryKey, Object... msgSummaryArgs) {
		addMessageInfo(componentId, msgSummaryKey, null, msgSummaryArgs, null);
	}
	
	/**
	 * Adiciona uma mensagem de severidade ERROR ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgDetailKey
	 * 	Chave do detalhamento a ser utilizado
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 * @param msgDetailArgs
	 * 	Argumentos do detalhamento.
	 */
	public void addMessageError(String componentId, String msgSummaryKey, String msgDetailKey, Object[] msgSummaryArgs,
			Object[] msgDetailArgs) {
		addMessage(componentId, FacesMessage.SEVERITY_ERROR, msgSummaryKey, msgDetailKey, msgSummaryArgs, msgDetailArgs);
	}
	
	/**
	 * Adiciona uma mensagem de severidade ERROR ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 */
	public void addMessageError(String componentId, String msgSummaryKey, Object... msgSummaryArgs) {
		addMessageError(componentId, msgSummaryKey, null, msgSummaryArgs, null);
	}
	
	/**
	 * Adiciona uma mensagem de severidade WARN ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgDetailKey
	 * 	Chave do detalhamento a ser utilizado
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 * @param msgDetailArgs
	 * 	Argumentos do detalhamento.
	 */
	public void addMessageWarn(String componentId, String msgSummaryKey, String msgDetailKey, Object[] msgSummaryArgs,
			Object[] msgDetailArgs) {
		addMessage(componentId, FacesMessage.SEVERITY_WARN, msgSummaryKey, msgDetailKey, msgSummaryArgs, msgDetailArgs);
	}
	
	/**
	 * Adiciona uma mensagem de severidade WARN ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 */
	public void addMessageWarn(String componentId, String msgSummaryKey, Object... msgSummaryArgs) {
		addMessageWarn(componentId, msgSummaryKey, null, msgSummaryArgs, null);
	}
	
	/**
	 * Adiciona uma mensagem de severidade FATAL ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgDetailKey
	 * 	Chave do detalhamento a ser utilizado
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 * @param msgDetailArgs
	 * 	Argumentos do detalhamento.
	 */
	public void addMessageFatal(String componentId, String msgSummaryKey, String msgDetailKey, Object[] msgSummaryArgs,
			Object[] msgDetailArgs) {
		addMessage(componentId, FacesMessage.SEVERITY_FATAL, msgSummaryKey, msgDetailKey, msgSummaryArgs, msgDetailArgs);
	}
	
	/**
	 * Adiciona uma mensagem de severidade FATAL ao {@link FacesContext}
	 * 
	 * @param componentId 
	 * 	Id do componente ao qual a mensagem será associada.
	 * @param msgSummaryKey
	 * 	Chave da mensagem a ser utilizada.
	 * @param msgSummaryArgs
	 * 	Argumentos da mensagem
	 */
	public void addMessageFatal(String componentId, String msgSummaryKey, Object... msgSummaryArgs) {
		addMessageFatal(componentId, msgSummaryKey, null, msgSummaryArgs, null);
	}
	
	/**
	 * Remove as mensagens contidas no {@link FacesContext}
	 */
	public void removeMessages() {
		getFacesContext().getMessageList().clear();
	}
	
	/**
	 * Recupera um componente a partir de um id e o seu componente pai.
	 * 
	 * @param parent
	 *            O componente pai.
	 * @param componentId
	 *            O id do componente a ser procurado.
	 * @return O componente encontrado.
	 */
	public UIComponent findComponent(UIComponent parent, String componentId) {
		UIComponent component = null;

		if (!componentId.equals(parent.getId())) {
			Iterator<UIComponent> children = parent.getFacetsAndChildren();
			for (Iterator<UIComponent> iterator = children; iterator.hasNext()
					&& (component == null);) {
				UIComponent child = iterator.next();
				component = findComponent(child, componentId);
			}
		} else {
			component = parent;
		}
		return component;
	}
	
	/**
	 * Recupera a instância do {@link FacesContext}.
	 * 
	 * @return
	 */
	public FacesContext getFacesContext() {
		return FacesContext.getCurrentInstance();
	}

	/**
	 * Recupera a instância do {@link ExternalContext}.
	 * 
	 * @return
	 */
	public ExternalContext getExternalContext() {
		return getFacesContext().getExternalContext();
	}

}
